package per.yan.learn.navplugin

import com.android.build.api.transform.Transform
import com.android.build.api.transform.Format
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.internal.pipeline.TransformManager
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSetContainer
import org.apache.commons.io.FileUtils
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassReader.EXPAND_FRAMES
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AnnotationNode
import per.yan.learn.navpluginruntime.NavData
import per.yan.learn.navpluginruntime.NavDestination
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.util.zip.ZipFile

class NavTransform(private val project: Project): Transform() {

    private var navDataList = mutableListOf<NavData>()

    companion object{
        const val NAV_RUNTIME_DESTINATION = "Lper/yan/learn/navpluginruntime/NavDestination;"
        const val NAV_RUNTIME_NAV_TYPE = "Lper/yan/learn/navpluginruntime/NavDestination\$NavType"

        private const val KEY_ROUTE = "route"
        private const val KEY_TYPE = "type"
        private const val KEY_START = "asStarter"


        private const val NAV_RUNTIME_PKG_NAME:String = "per.yan.learn.navpluginruntime"
        private const val NAV_RUNTIME_REGISTRY_CLASS_NAME:String = "NavRegistry"
        private const val NAV_RUNTIME_NAV_DATA_CLASS_NAME:String = "NavData"
        private const val NAV_RUNTIME_NAV_LIST:String = "navList"
        private const val NAV_RUNTIME_MODULE_NAME:String = "nav-plugin-runtime"


    }


    override fun getName(): String = "NavTransform"

    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> = TransformManager.CONTENT_CLASS

    override fun getScopes(): MutableSet<in QualifiedContent.Scope> = TransformManager.SCOPE_FULL_PROJECT

    override fun isIncremental(): Boolean = false


    override fun transform(transformInvocation: TransformInvocation?) {
        navDataList.clear()
        val inputs = transformInvocation?.inputs?:return
        val outputProvider = transformInvocation.outputProvider
        inputs.forEach{
            it.jarInputs.forEach {
                handJarClasses(it.file)
                val outputDir = outputProvider.getContentLocation(
                    it.name,
                    it.contentTypes,
                    it.scopes,
                    Format.JAR
                )

                if(it.file.isFile){
                    FileUtils.copyFile(it.file , outputDir)
                }else{
                    FileUtils.copyDirectory(it.file , outputDir)
                }

            }
            it.directoryInputs.forEach {
                handDirectoryClasses(it.file)
                val outputDir = outputProvider.getContentLocation(
                    it.name,
                    it.contentTypes,
                    it.scopes,
                    Format.DIRECTORY
                )

                if(it.file.isFile){
                    FileUtils.copyFile(it.file , outputDir)
                }else{
                    FileUtils.copyDirectory(it.file , outputDir)
                }
            }
        }
        generateNavRegistry()

//        super.transform(transformInvocation)
    }

    private fun generateNavRegistry() {

        /**
         *  val navList = ArrayList<NavData>()
         */
        val dataClass = ClassName(NAV_RUNTIME_PKG_NAME , NAV_RUNTIME_NAV_DATA_CLASS_NAME)
        val arrayList = ClassName("kotlin.collections", "ArrayList")
        val list = ClassName("kotlin.collections", "List")

        val arrOfNavData = arrayList.parameterizedBy(dataClass)
        val listOfNavData = list.parameterizedBy(dataClass)
        val property = PropertySpec.builder(NAV_RUNTIME_NAV_LIST , arrOfNavData , KModifier.PRIVATE)
            .initializer(CodeBlock.builder().addStatement("ArrayList<NavData>()").build())
            .build()

        /**
         * 构建init函数
         */
        val statements = java.lang.StringBuilder()
        navDataList.forEach{
            val buildStr = "navList.add(NavData(\"${it.route}\",\"${it.className}\", ${it.asStart} ,${it.type}))"
            println("nav-plugin....  :::::$buildStr")
            statements.append(buildStr).append("\n")
        }
        println("nav-plugin......${navDataList.size}")

        /**
         * 构建getList Function
         */
        val function = FunSpec.builder("get")
            .returns(listOfNavData)
            .addCode(CodeBlock.builder().addStatement("val list = ArrayList<NavData>()\n list.addAll(navList)\n return list\n").build())
            .build()


        val typeSpec = TypeSpec.objectBuilder(NAV_RUNTIME_REGISTRY_CLASS_NAME)
            .addProperty(property)
            .addInitializerBlock(CodeBlock.builder().addStatement(statements.toString()).build())
            .addFunction(function)
            .build()

        val fileSpec = FileSpec.builder(NAV_RUNTIME_PKG_NAME , NAV_RUNTIME_REGISTRY_CLASS_NAME)
            .addComment("自动生成文件,请不要随便修改和删除")
            .addType(typeSpec)
            .addImport(NavDestination.NavType::class.java , "Fragment","Dialog","Activity","None")
            .build()

        //写入文件
        val runtimeProject = project.rootProject.findProject(NAV_RUNTIME_MODULE_NAME)

        assert(runtimeProject == null ){
            throw GradleException("找不到nav-plugin-runtime模块")
        }

        val setContainer =
            runtimeProject!!.extensions.findByName("sourceSets") as SourceSetContainer

        val outputFileDir = setContainer.first().java.srcDirs.first().absoluteFile

        fileSpec.writeTo(outputFileDir)

    }

    private fun handDirectoryClasses(file: File) {
        if(file.isDirectory){
            file.listFiles()?.forEach {
                handDirectoryClasses(it)
            }
        }else if(file.extension.endsWith("class")){
            println("NavTransform handleDirectoryClasses-zipEntry:${file.name}")
            FileInputStream(file).use {
                visitClass(it)
            }
        }

    }

    private fun handJarClasses(file: File) {
        val zipFile = ZipFile(file)
        zipFile.stream().forEach {
            if(it.name.endsWith("class")){
                zipFile.getInputStream(it).use{
                    visitClass(it)
                }
            }
        }
        zipFile.close()
    }

    private fun visitClass(inputStream:InputStream){
        val classReader = ClassReader(inputStream)

        val classVisitor = object :ClassVisitor(Opcodes.ASM9){
            override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {

//                println("visitAnnotation1:$descriptor")
//                println("visitAnnotation2:$NAV_RUNTIME_DESTINATION")
                if(descriptor != NAV_RUNTIME_DESTINATION){
                    return object :AnnotationVisitor(Opcodes.ASM9){}
                }


                val annotationVisitor = object : AnnotationNode(Opcodes.ASM9 , ""){
                    var route = ""
                    var asStarter:Boolean = false;
                    var type = NavDestination.NavType.None
                    override fun visit(name: String?, value: Any?) {
                        super.visit(name, value)
                        if(name == KEY_ROUTE){
                            route = value as String
                        }else if(name == KEY_START){
                            asStarter = value as Boolean
                            println("nav-plugin.... catch as Startrt-----$asStarter")
                        }
                    }

                    override fun visitEnum(name: String?, descriptor: String?, value: String?) {
                        super.visitEnum(name, descriptor, value)
                        if(name == KEY_TYPE){
                            value?.let{
                                type = NavDestination.NavType.valueOf(value)
                            }
                        }

                    }

                    override fun visitEnd() {
                        super.visitEnd()
                        val navData = NavData(route, classReader.className.replace("/", "."), asStarter , type)
                        println("nav-plugin...."+classReader.className.replace("/", ".")+".."+type.name)
                        navDataList.add(navData)
                    }
                }
                return annotationVisitor
            }
        }
        classReader.accept(classVisitor , EXPAND_FRAMES)
    }
}